package org.jivesoftware.smack.packet;

/**
 * A privacy item acts a rule that when matched defines if a packet should be
 * blocked or not.
 * 
 * Privacy Items can handle different kind of blocking communications based on
 * JID, group, subscription type or globally by:
 * <ul>
 * <li>Allowing or blocking messages.
 * <li>Allowing or blocking inbound presence notifications.
 * <li>Allowing or blocking outbound presence notifications.
 * <li>Allowing or blocking IQ stanzas.
 * <li>Allowing or blocking all communications.
 * </ul>
 * 
 * @author Francisco Vives
 */
public class PrivacyItem {
	/**
	 * allow is the action associated with the item, it can allow or deny the
	 * communication.
	 */
	private boolean allow;
	/**
	 * order is a non-negative integer that is unique among all items in the
	 * list.
	 */
	private int order;
	/**
	 * rule hold the kind of communication ([jid|group|subscription]) it will
	 * allow or block and identifier to apply the action. If the type is "jid",
	 * then the 'value' attribute MUST contain a valid Jabber ID. If the type is
	 * "group", then the 'value' attribute SHOULD contain the name of a group in
	 * the user's roster. If the type is "subscription", then the 'value'
	 * attribute MUST be one of "both", "to", "from", or "none".
	 */
	private PrivacyRule rule;

	/** blocks incoming IQ stanzas. */
	private boolean filterIQ = false;
	/** filterMessage blocks incoming message stanzas. */
	private boolean filterMessage = false;
	/** blocks incoming presence notifications. */
	private boolean filterPresence_in = false;
	/** blocks outgoing presence notifications. */
	private boolean filterPresence_out = false;

	/**
	 * Creates a new privacy item.
	 * 
	 * @param type
	 *            the type.
	 */
	public PrivacyItem(String type, boolean allow, int order) {
		this.setRule(PrivacyRule.fromString(type));
		this.setAllow(allow);
		this.setOrder(order);
	}

	/**
	 * Returns the action associated with the item, it MUST be filled and will
	 * allow or deny the communication.
	 * 
	 * @return the allow communication status.
	 */
	public boolean isAllow() {
		return allow;
	}

	/**
	 * Sets the action associated with the item, it can allow or deny the
	 * communication.
	 * 
	 * @param allow
	 *            indicates if the receiver allow or deny the communication.
	 */
	private void setAllow(boolean allow) {
		this.allow = allow;
	}

	/**
	 * Returns whether the receiver allow or deny incoming IQ stanzas or not.
	 * 
	 * @return the iq filtering status.
	 */
	public boolean isFilterIQ() {
		return filterIQ;
	}

	/**
	 * Sets whether the receiver allows or denies incoming IQ stanzas or not.
	 * 
	 * @param filterIQ
	 *            indicates if the receiver allows or denies incoming IQ
	 *            stanzas.
	 */
	public void setFilterIQ(boolean filterIQ) {
		this.filterIQ = filterIQ;
	}

	/**
	 * Returns whether the receiver allows or denies incoming messages or not.
	 * 
	 * @return the message filtering status.
	 */
	public boolean isFilterMessage() {
		return filterMessage;
	}

	/**
	 * Sets wheather the receiver allows or denies incoming messages or not.
	 * 
	 * @param filterMessage
	 *            indicates if the receiver allows or denies incoming messages
	 *            or not.
	 */
	public void setFilterMessage(boolean filterMessage) {
		this.filterMessage = filterMessage;
	}

	/**
	 * Returns whether the receiver allows or denies incoming presence or not.
	 * 
	 * @return the iq filtering incoming presence status.
	 */
	public boolean isFilterPresence_in() {
		return filterPresence_in;
	}

	/**
	 * Sets whether the receiver allows or denies incoming presence or not.
	 * 
	 * @param filterPresence_in
	 *            indicates if the receiver allows or denies filtering incoming
	 *            presence.
	 */
	public void setFilterPresence_in(boolean filterPresence_in) {
		this.filterPresence_in = filterPresence_in;
	}

	/**
	 * Returns whether the receiver allows or denies incoming presence or not.
	 * 
	 * @return the iq filtering incoming presence status.
	 */
	public boolean isFilterPresence_out() {
		return filterPresence_out;
	}

	/**
	 * Sets whether the receiver allows or denies outgoing presence or not.
	 * 
	 * @param filterPresence_out
	 *            indicates if the receiver allows or denies filtering outgoing
	 *            presence
	 */
	public void setFilterPresence_out(boolean filterPresence_out) {
		this.filterPresence_out = filterPresence_out;
	}

	/**
	 * Returns the order where the receiver is processed. List items are
	 * processed in ascending order.
	 * 
	 * The order MUST be filled and its value MUST be a non-negative integer
	 * that is unique among all items in the list.
	 * 
	 * @return the order number.
	 */
	public int getOrder() {
		return order;
	}

	/**
	 * Sets the order where the receiver is processed.
	 * 
	 * The order MUST be filled and its value MUST be a non-negative integer
	 * that is unique among all items in the list.
	 * 
	 * @param order
	 *            indicates the order in the list.
	 */
	public void setOrder(int order) {
		this.order = order;
	}

	/**
	 * Sets the element identifier to apply the action.
	 * 
	 * If the type is "jid", then the 'value' attribute MUST contain a valid
	 * Jabber ID. If the type is "group", then the 'value' attribute SHOULD
	 * contain the name of a group in the user's roster. If the type is
	 * "subscription", then the 'value' attribute MUST be one of "both", "to",
	 * "from", or "none".
	 * 
	 * @param value
	 *            is the identifier to apply the action.
	 */
	public void setValue(String value) {
		if (!(this.getRule() == null && value == null)) {
			this.getRule().setValue(value);
		}
	}

	/**
	 * Returns the type hold the kind of communication it will allow or block.
	 * It MUST be filled with one of these values: jid, group or subscription.
	 * 
	 * @return the type of communication it represent.
	 */
	public Type getType() {
		if (this.getRule() == null) {
			return null;
		} else {
			return this.getRule().getType();
		}
	}

	/**
	 * Returns the element identifier to apply the action.
	 * 
	 * If the type is "jid", then the 'value' attribute MUST contain a valid
	 * Jabber ID. If the type is "group", then the 'value' attribute SHOULD
	 * contain the name of a group in the user's roster. If the type is
	 * "subscription", then the 'value' attribute MUST be one of "both", "to",
	 * "from", or "none".
	 * 
	 * @return the identifier to apply the action.
	 */
	public String getValue() {
		if (this.getRule() == null) {
			return null;
		} else {
			return this.getRule().getValue();
		}
	}

	/**
	 * Returns whether the receiver allows or denies every kind of
	 * communication.
	 * 
	 * When filterIQ, filterMessage, filterPresence_in and filterPresence_out
	 * are not set the receiver will block all communications.
	 * 
	 * @return the all communications status.
	 */
	public boolean isFilterEverything() {
		return !(this.isFilterIQ() || this.isFilterMessage()
				|| this.isFilterPresence_in() || this.isFilterPresence_out());
	}

	private PrivacyRule getRule() {
		return rule;
	}

	private void setRule(PrivacyRule rule) {
		this.rule = rule;
	}

	/**
	 * Answer an xml representation of the receiver according to the RFC 3921.
	 * 
	 * @return the text xml representation.
	 */
	public String toXML() {
		StringBuilder buf = new StringBuilder();
		buf.append("<item");
		if (this.isAllow()) {
			buf.append(" action=\"allow\"");
		} else {
			buf.append(" action=\"deny\"");
		}
		buf.append(" order=\"").append(getOrder()).append("\"");
		if (getType() != null) {
			buf.append(" type=\"").append(getType()).append("\"");
		}
		if (getValue() != null) {
			buf.append(" value=\"").append(getValue()).append("\"");
		}
		if (isFilterEverything()) {
			buf.append("/>");
		} else {
			buf.append(">");
			if (this.isFilterIQ()) {
				buf.append("<iq/>");
			}
			if (this.isFilterMessage()) {
				buf.append("<message/>");
			}
			if (this.isFilterPresence_in()) {
				buf.append("<presence-in/>");
			}
			if (this.isFilterPresence_out()) {
				buf.append("<presence-out/>");
			}
			buf.append("</item>");
		}
		return buf.toString();
	}

	/**
	 * Privacy Rule represents the kind of action to apply. It holds the kind of
	 * communication ([jid|group|subscription]) it will allow or block and
	 * identifier to apply the action.
	 */

	public static class PrivacyRule {
		/**
		 * Type defines if the rule is based on JIDs, roster groups or presence
		 * subscription types. Available values are: [jid|group|subscription]
		 */
		private Type type;
		/**
		 * The value hold the element identifier to apply the action. If the
		 * type is "jid", then the 'value' attribute MUST contain a valid Jabber
		 * ID. If the type is "group", then the 'value' attribute SHOULD contain
		 * the name of a group in the user's roster. If the type is
		 * "subscription", then the 'value' attribute MUST be one of "both",
		 * "to", "from", or "none".
		 */
		private String value;

		/**
		 * If the type is "subscription", then the 'value' attribute MUST be one
		 * of "both", "to", "from", or "none"
		 */
		public static final String SUBSCRIPTION_BOTH = "both";
		public static final String SUBSCRIPTION_TO = "to";
		public static final String SUBSCRIPTION_FROM = "from";
		public static final String SUBSCRIPTION_NONE = "none";

		/**
		 * Returns the type constant associated with the String value.
		 */
		protected static PrivacyRule fromString(String value) {
			if (value == null) {
				return null;
			}
			PrivacyRule rule = new PrivacyRule();
			rule.setType(Type.valueOf(value.toLowerCase()));
			return rule;
		}

		/**
		 * Returns the type hold the kind of communication it will allow or
		 * block. It MUST be filled with one of these values: jid, group or
		 * subscription.
		 * 
		 * @return the type of communication it represent.
		 */
		public Type getType() {
			return type;
		}

		/**
		 * Sets the action associated with the item, it can allow or deny the
		 * communication.
		 * 
		 * @param type
		 *            indicates if the receiver allows or denies the
		 *            communication.
		 */
		private void setType(Type type) {
			this.type = type;
		}

		/**
		 * Returns the element identifier to apply the action.
		 * 
		 * If the type is "jid", then the 'value' attribute MUST contain a valid
		 * Jabber ID. If the type is "group", then the 'value' attribute SHOULD
		 * contain the name of a group in the user's roster. If the type is
		 * "subscription", then the 'value' attribute MUST be one of "both",
		 * "to", "from", or "none".
		 * 
		 * @return the identifier to apply the action.
		 */
		public String getValue() {
			return value;
		}

		/**
		 * Sets the element identifier to apply the action.
		 * 
		 * If the type is "jid", then the 'value' attribute MUST contain a valid
		 * Jabber ID. If the type is "group", then the 'value' attribute SHOULD
		 * contain the name of a group in the user's roster. If the type is
		 * "subscription", then the 'value' attribute MUST be one of "both",
		 * "to", "from", or "none".
		 * 
		 * @param value
		 *            is the identifier to apply the action.
		 */
		protected void setValue(String value) {
			if (this.isSuscription()) {
				setSuscriptionValue(value);
			} else {
				this.value = value;
			}
		}

		/**
		 * Sets the element identifier to apply the action.
		 * 
		 * The 'value' attribute MUST be one of "both", "to", "from", or "none".
		 * 
		 * @param value
		 *            is the identifier to apply the action.
		 */
		private void setSuscriptionValue(String value) {
			String setValue;
			if (value == null) {
				// Do nothing
			}
			if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) {
				setValue = SUBSCRIPTION_BOTH;
			} else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) {
				setValue = SUBSCRIPTION_TO;
			} else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) {
				setValue = SUBSCRIPTION_FROM;
			} else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) {
				setValue = SUBSCRIPTION_NONE;
			}
			// Default to available.
			else {
				setValue = null;
			}
			this.value = setValue;
		}

		/**
		 * Returns if the receiver represents a subscription rule.
		 * 
		 * @return if the receiver represents a subscription rule.
		 */
		public boolean isSuscription() {
			return this.getType() == Type.subscription;
		}
	}

	/**
	 * Type defines if the rule is based on JIDs, roster groups or presence
	 * subscription types.
	 */
	public static enum Type {
		/**
		 * JID being analyzed should belong to a roster group of the list's
		 * owner.
		 */
		group,
		/**
		 * JID being analyzed should have a resource match, domain match or bare
		 * JID match.
		 */
		jid,
		/**
		 * JID being analyzed should belong to a contact present in the owner's
		 * roster with the specified subscription status.
		 */
		subscription
	}
}
